package com.ncmmall.api.apis.mall.wechat;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import com.ncmmall.api.apis.service.Result;
import com.ncmmall.api.security.Securitys;
import com.ncmmall.application.business.order.BillLadingApplication;
import com.ncmmall.application.business.order.OrderApplication;
import com.ncmmall.application.util.AlipayUtil;
import com.ncmmall.application.util.WechatPayUtil;
import com.feijin.commons.lang.ArithUtils;
import com.feijin.commons.lang.MD5s;
import com.feijin.commons.lang.PropertiesHelps;
import com.feijin.commons.lang.Randoms;
import com.feijin.commons.webs.SyncLock;
import com.feijin.commons.xml.Xmls;
import com.ncmmall.domain.business.order.Order;
import com.ncmmall.domain.business.order.QOrder;
import com.ncmmall.domain.business.order.dto.OrderDTO;
import com.ncmmall.domain.business.order.dto.SupplierOrderDTO;
import com.ncmmall.domain.business.order.lading.BillOfLading;
import com.ncmmall.domain.business.order.lading.QBillOfLading;
import com.ncmmall.domain.security.user.WebUser;
import com.ncmmall.querychannel.service.QueryChannelService;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 微信小程序支付
 *
 * @author likaikai
 * @date 2019-07-17 14:47:20
 */
@RestController
@RequestMapping(value = "/wechat/pay")
public class WechatPayApi {

    private static final Logger log = LoggerFactory.getLogger(WechatPayApi.class);

    @Autowired
    private QueryChannelService queryChannelService;

    @Autowired
    private OrderApplication orderApplication;

    @Autowired
    private BillLadingApplication billLadingApplication;

    /**
     * 支付状态 已支付
     */
    private static final int PAYSTATUS_SUCCESS = 2;

    /**
     * 电商小程序支付商品订单，微信支付
     */
    @RequestMapping(value = "/order", method = RequestMethod.POST)
    @ResponseBody
    public Result payOrder(@RequestBody SupplierOrderDTO orderParams, HttpServletRequest request) {
        Result result = new Result();

        try {
            WebUser member = Securitys.user(request);
            List<Long> ids = orderParams.getOrders().stream().map(OrderDTO::getId).collect(Collectors.toList());
            List<Order> orders = queryChannelService.findAll(QOrder.order.id.in(ids).and(QOrder.order.member.eq(member)), Order.class);
            if (CollUtil.isEmpty(orders)) {
                return result.fail().setMsg("订单不存在");
            }

            for (Order order : orders) {
                if (order.getStatus() == PAYSTATUS_SUCCESS) {
                    return result.fail().setMsg("非法操作");
                }
            }
            String payNo = new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date()) + member.getId() + Randoms.generateDigitalString(3);

            double price = 0;
            int payType = orderParams.getOrders().get(0).getPayType();

            for (Order order : orders) {
                price = ArithUtils.add(price, order.getPrice());
                order.setIsBlanceOrder(orderParams.getIsBlanceOrder());
                if (order.getIsBlanceOrder() == 1) {    //存在余额抵扣
                    price = ArithUtils.sub(price, member.getBalance());
                    orderApplication.blanceOrderPay(payNo, order);
                } else {
                    if (payType == 1) {
                        orderApplication.wechatAppPay(payNo, order);
                    } else if (payType == 2) {
                        orderApplication.alipayAppPay(payNo, order);
                    } else {
                        orderApplication.wechatPay(payNo, order);
                    }
                }
            }

            if (price <= 0) {
                return result.fail().setMsg("");
            }

            if (payType == 1) {
                Map<String, String> data = WechatPayUtil.createAppOrder("拖拖部落采购订单支付", payNo, price, PropertiesHelps.getValue("wx.app_notify_url_order"));
                return result.ok().inject(data);
            }

            if (payType == 2) {
                String body = AlipayUtil.createAppOrder("拖拖部落采购订单支付", payNo, price, PropertiesHelps.getValue("alipay.notify_url_order"));
                return result.ok().inject(ImmutableMap.of("body", body));
            }

            WechatPayOrderResponse response = new WechatPay().mpOrder(payNo, price, "拖拖部落采购订单支付", member.getOpenId());
            Map<String, String> data = new HashMap<>();
            data.put("appid", PropertiesHelps.getValue("wx.sub_appid"));
            data.put("nonceStr", Randoms.getRandomCharAndNumr(32));
            data.put("package", "prepay_id=" + response.getPrepay_id());
            data.put("signType", "MD5");
            data.put("timeStamp", String.valueOf(System.currentTimeMillis()).substring(0, 10));
            String sign = buildMp(data);
            data.put("paySign", sign);

            return result.ok().inject(data);

        } catch (Exception e) {
            log.error("微信支付统一下单异常", e);
            return result.fail().setMsg("处理异常");
        }
    }

    private String buildMp(Map<String, String> data) {
        String sign = "appId=" + data.get("appid") + "&nonceStr=" + data.get("nonceStr") + "&package=" + data.get("package") + "&signType=" + data.get("signType") + "&timeStamp=" + data.get("timeStamp") + "&key=" + PropertiesHelps.getValue("wx.key");
        return MD5s.execute(sign).toUpperCase();
    }

    /**
     * 商品订单支付宝支付回调
     */
    @RequestMapping(value = "/alipay/order/callback")
    @ResponseBody
    public String alipayCallback(HttpServletRequest request) {
        try {

            Map<String, String> map = parseRequestParam(request);
            String outTradeNo = MapUtil.getStr(map, "out_trade_no");
            String tradeStatus = MapUtil.getStr(map, "trade_status");

            synchronized (SyncLock.getLock(outTradeNo)) {
                if (!AlipayUtil.verifyNotify(map)) {
                    log.error("[支付宝支付回调通知签名校验不通过]");
                    return "";
                }

                List<Order> orders = queryChannelService.findAll(QOrder.order.payNo.eq(outTradeNo), Order.class);
                if (orders.isEmpty()) {
                    log.error("[支付宝支付回调，订单不存在，交易流水号]" + outTradeNo);
                    return "";
                }

                orders.removeIf(order -> order.getStatus() != 1);
                if (orders.isEmpty()) {
                    log.error("[支付宝支付回调，订单不存在，交易流水号]" + outTradeNo);
                    return "";
                }

                if ("TRADE_SUCCESS".equalsIgnoreCase(tradeStatus) || "TRADE_FINISHED".equalsIgnoreCase(tradeStatus)) {
                    orderApplication.paySuccess(orders);
                    return "success";
                }

                return "";
            }
        } catch (Exception e) {
            log.error("[支付宝支付回调处理异常]", e);
            return "";
        }
    }

    private Map<String, String> parseRequestParam(HttpServletRequest request) {
        Map<String, String[]> map = request.getParameterMap();
        Map<String, String> map2 = new HashMap<>();
        for (Map.Entry<String, String[]> entry : map.entrySet()) {
            map2.put(entry.getKey(), ArrayUtil.join(entry.getValue(), ""));
        }
        return map2;
    }


    /**
     * 商品订单微信支付回调
     */
    @RequestMapping(value = "/order/callback", method = RequestMethod.POST)
    @ResponseBody
    public String callback(HttpServletRequest request) {
        try {
            String xml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            log.info("[微信支付异步回调通知报文]:" + xml);
            WechatPayCallbackRequestDTO callback = Xmls.converyToObject(xml, WechatPayCallbackRequestDTO.class);

            synchronized (SyncLock.getLock(callback.getOut_trade_no())) {

                if (!"SUCCESS".equalsIgnoreCase(callback.getReturn_code())) {
                    //响应不成功
                    return buildResponse("FAIL", "return_code不为SUCCESS");
                }

                //商户密钥
                String key = PropertiesHelps.getValue("wx.key");
                String sign = buildSign(callback, key);

                if (!sign.equals(callback.getSign())) {
                    log.error("[微信支付回调通知签名校验不通过]");
                    return buildResponse("FAIL", "签名校验不通过");
                }

                //这里要先校验一下订单是否已经支付过了,如果已经支付过了直接返回成功,通过callback.getOut_trade_no()获取订单
                List<Order> orders = queryChannelService.findAll(QOrder.order.payNo.eq(callback.getOut_trade_no()), Order.class);

                if (orders.isEmpty()) {
                    log.warn("[微信支付回调，订单不存在，交易流水号]" + callback.getOut_trade_no());
                    return buildResponse("result", "订单不存在");
                }

                for (Order order : orders) {
                    if (1 == order.getPayStatus()) {
                        return buildResponse("result", "已支付,请勿重复提交");
                    }
                }

                if ("SUCCESS".equalsIgnoreCase(callback.getResult_code())) {
                    String trade_state = new WechatPay().orderQuery(callback.getOut_trade_no());
                    if ("SUCCESS".equals(trade_state)) {
                        log.info("[微信支付回调支付成功]");

                        orderApplication.paySuccess(orders);

                        return buildResponse("SUCCESS", "支付成功");

                    } else {
                        log.error("[微信支付二次校验订单不通过]");
                        return buildResponse("FAIL", "二次校验订单不通过");
                    }
                }
                return buildResponse("FAIL", "支付未成功");
            }

        } catch (Exception e) {
            log.error("[微信支付回调处理异常]", e);
            return buildResponse("FAIL", "处理异常");
        }
    }


    /**
     * 商品订单微信支付回调
     */
    @RequestMapping(value = "/app/order/callback", method = RequestMethod.POST)
    @ResponseBody
    public String appCallback(HttpServletRequest request) {
        try {
            String xml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            log.info("[微信支付异步回调通知报文]:" + xml);
            Map<String, String> map = WXPayUtil.xmlToMap(xml);

            String returnCode = MapUtil.getStr(map, "return_code");
            String outTradeNo = MapUtil.getStr(map, "out_trade_no");

            if (!"SUCCESS".equalsIgnoreCase(returnCode)) {
                return buildResponse("FAIL", "return_code不为SUCCESS");
            }

            synchronized (SyncLock.getLock(outTradeNo)) {

                if (!WechatPayUtil.verifyNotify(map)) {
                    log.error("[微信支付回调通知签名校验不通过]");
                    return buildResponse("FAIL", "签名校验不通过");
                }

                List<Order> orders = queryChannelService.findAll(QOrder.order.payNo.eq(outTradeNo), Order.class);
                if (orders.isEmpty()) {
                    log.warn("[微信支付回调，订单不存在，交易流水号]" + outTradeNo);
                    return buildResponse("result", "订单不存在");
                }

                for (Order order : orders) {
                    if (1 == order.getPayStatus()) {
                        return buildResponse("result", "已支付,请勿重复提交");
                    }
                }

                if (WechatPayUtil.tradeQuery(outTradeNo)) {
                    log.info("[微信支付回调支付成功]");
                    orderApplication.paySuccess(orders);
                    return buildResponse("SUCCESS", "支付成功");
                } else {
                    log.error("[微信支付二次校验订单不通过]");
                    return buildResponse("FAIL", "二次校验订单不通过");
                }
            }
        } catch (Exception e) {
            log.error("[微信支付回调处理异常]", e);
            return buildResponse("FAIL", "处理异常");
        }
    }


    /**
     * 提货单运费回调
     */
    @RequestMapping(value = "/billLading/callback", method = RequestMethod.POST)
    @ResponseBody
    public String billLadingCallBack(HttpServletRequest request) {
        try {
            String xml = IOUtils.toString(request.getInputStream(), StandardCharsets.UTF_8);
            log.info("[微信支付异步回调通知报文]:" + xml);
            WechatPayCallbackRequestDTO callback = Xmls.converyToObject(xml, WechatPayCallbackRequestDTO.class);

            synchronized (SyncLock.getLock(callback.getOut_trade_no())) {

                if (!"SUCCESS".equalsIgnoreCase(callback.getReturn_code())) {
                    //响应不成功
                    return buildResponse("FAIL", "return_code不为SUCCESS");
                }

                //商户密钥
                String key = PropertiesHelps.getValue("wx.key2");
                String sign = buildSign2(callback, key);

                if (!sign.equals(callback.getSign())) {
                    log.error("[微信支付回调通知签名校验不通过]");
                    return buildResponse("FAIL", "签名校验不通过");
                }

                //这里要先校验一下订单是否已经支付过了,如果已经支付过了直接返回成功,通过callback.getOut_trade_no()获取订单
                BillOfLading billOfLading = queryChannelService.findOne(QBillOfLading.billOfLading.payNo.eq(callback.getOut_trade_no()), BillOfLading.class);


                if (billOfLading == null) {
                    log.warn("[微信支付回调，订单不存在，交易流水号]" + callback.getOut_trade_no());
                    return buildResponse("result", "订单不存在");
                }

                if (1 == billOfLading.getPayStatus()) {
                    return buildResponse("result", "已支付,请勿重复提交");
                }

                if ("SUCCESS".equalsIgnoreCase(callback.getResult_code())) {
                    String trade_state = new WechatPay().orderQuery2(callback.getOut_trade_no());
                    if ("SUCCESS".equals(trade_state)) {
                        log.info("[微信支付回调支付成功]");
                        billLadingApplication.wechatPaySuccess(billOfLading);
                        return buildResponse("SUCCESS", "支付成功");
                    } else {
                        log.error("[微信支付二次校验订单不通过]");
                        return buildResponse("FAIL", "二次校验订单不通过");
                    }
                }
                return buildResponse("FAIL", "支付未成功");
            }

        } catch (Exception e) {
            log.error("[微信支付回调处理异常]", e);
            return buildResponse("FAIL", "处理异常");
        }
    }

    private String buildResponse(String status, String msg) {
        WechatPayCallbackResponseDTO response = new WechatPayCallbackResponseDTO();
        response.setReturn_code(status);
        response.setReturn_msg(msg);
        return Xmls.convertToXml(response, false);
    }


    private String buildSign2(WechatPayCallbackRequestDTO callback, String key) {

        String sign = "appid=" + callback.getAppid() + "&bank_type=" + callback.getBank_type() + "&cash_fee=" + callback.getCash_fee() + "&fee_type=" + callback.getFee_type() + "&is_subscribe=" + callback.getIs_subscribe() + "&mch_id=" + callback.getMch_id() + "&nonce_str=" + callback.getNonce_str() + "&openid=" + callback.getOpenid() + "&out_trade_no=" + callback.getOut_trade_no() + "&result_code=" + callback.getResult_code() + "&return_code=" + callback.getReturn_code() + "&time_end=" + callback.getTime_end() + "&total_fee=" + callback.getTotal_fee() + "&trade_type=" + callback.getTrade_type() + "&transaction_id=" + callback.getTransaction_id() + "&key=" + key;
        return MD5s.execute(sign).toUpperCase();
    }


    private String buildSign(WechatPayCallbackRequestDTO callback, String key) {

        String sign = "appid=" + callback.getAppid() + "&bank_type=" + callback.getBank_type() + "&cash_fee=" + callback.getCash_fee() + "&fee_type=" + callback.getFee_type() + "&is_subscribe=" + callback.getIs_subscribe() + "&mch_id=" + callback.getMch_id() + "&nonce_str=" + callback.getNonce_str() + "&openid=" + callback.getOpenid() + "&out_trade_no=" + callback.getOut_trade_no() + "&result_code=" + callback.getResult_code() + "&return_code=" + callback.getReturn_code() + "&sub_appid=" + callback.getSub_appid() + "&sub_is_subscribe=" + callback.getSub_is_subscribe() + "&sub_mch_id=" + callback.getSub_mch_id() + "&sub_openid=" + callback.getSub_openid() + "&time_end=" + callback.getTime_end() + "&total_fee=" + callback.getTotal_fee() + "&trade_type=" + callback.getTrade_type() + "&transaction_id=" + callback.getTransaction_id() + "&key=" + key;
        return MD5s.execute(sign).toUpperCase();
    }


}
